package taskhandler

import (
	"cvevulner/common"
	"cvevulner/models"
	"errors"
	"fmt"
	"github.com/360EntSecGroup-Skylar/excelize/v2"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"path/filepath"
	"strconv"
)

const sheetName = "CVE_list"

type IssueStr struct {
	cveCount     int64
	HighCveCount int64
	CurIndex     int64
}

func GetIssueData(beforeMonth, prcnum int, templateId int64) ([]models.IssueTemplate, error) {
	beforeTime := common.GetBeforeDate(2, beforeMonth)
	it, err := models.QueryIssueTemplateByTime(beforeTime, prcnum, templateId, 3)
	return it, err
}

func GetNoLinkIssueData(beforeDate, prcnum int, templateId int64) ([]models.IssueTemplate, error) {
	beforeTime := common.GetBeforeTime(beforeDate)
	it, err := models.QueryIssueTemplateByLink(beforeTime, prcnum, templateId, 3)
	return it, err
}

func createExcel(beforeMonth int) (string, string) {
	beforeTime := common.GetBeforeDate(2, beforeMonth)
	// File storage directory
	dir := beego.AppConfig.String("fileDir")
	common.CreateDir(dir)
	excelName := ""
	if len(beforeTime) > 10 {
		excelName = "CVE_ISSUE_" + beforeTime[:10] + "_" + common.GetCurDate() + ".xlsx"
	} else {
		excelName = "CVE_ISSUE_" + beforeTime + "_" + common.GetCurDate() + ".xlsx"
	}
	excelPath := filepath.Join(dir, excelName)
	xlsx := excelize.NewFile()
	index := xlsx.NewSheet(sheetName)
	sheetTileMap := make(map[string]string)
	sheetTileMap["A1"] = "序号"
	sheetTileMap["B1"] = "issueID"
	sheetTileMap["C1"] = "issue标题"
	sheetTileMap["D1"] = "issue责任人"
	sheetTileMap["E1"] = "issue状态"
	sheetTileMap["F1"] = "issue创建时间"
	sheetTileMap["G1"] = "issue所属仓库"
	sheetTileMap["H1"] = "CVSS评分"
	sheetTileMap["I1"] = "评分级别"
	for k, v := range sheetTileMap {
		xlsx.SetCellValue(sheetName, k, v)
	}
	xlsx.SetActiveSheet(index)
	err := xlsx.SaveAs(excelPath)
	if err != nil {
		logs.Error(err)
		return "", ""
	}
	return excelPath, dir
}

func CveCredExcel() (string, string) {
	// File storage directory
	dir := beego.AppConfig.String("fileDir")
	common.CreateDir(dir)
	excelName := "CVE_Low_Credibility_" + common.GetCurDate() + ".xlsx"
	excelPath := filepath.Join(dir, excelName)
	xlsx := excelize.NewFile()
	index := xlsx.NewSheet(sheetName)
	sheetTileMap := make(map[string]string)
	sheetTileMap["A1"] = "CVE编号"
	sheetTileMap["B1"] = "CVE影响的组件和版本"
	sheetTileMap["C1"] = "CVE描述"
	sheetTileMap["D1"] = "CVE可信等级(4:包名版本号通过SA获取、版本号为修复版本号、修复版本以下都视为受影响版本;5:包名版本号通过SA获取并通过别名匹配、版本号为修复版本号、修复版本以下都视为受影响版本;6:版本号未匹配、包名通过以上任意方式匹配、可信度最低)"
	sheetTileMap["E1"] = "CVE发布时间"
	for k, v := range sheetTileMap {
		xlsx.SetCellValue(sheetName, k, v)
	}
	xlsx.SetActiveSheet(index)
	err := xlsx.SaveAs(excelPath)
	if err != nil {
		logs.Error(err)
		return "", ""
	}
	return excelPath, dir
}

func procCredData(org models.OriginUpstream, desc models.OriginUpstreamDesc) []interface{} {
	cveData := make([]interface{}, 0)
	cveData = append(cveData, org.CveNum)
	cveData = append(cveData, org.PackName)
	cveData = append(cveData, desc.EnDescription)
	cveData = append(cveData, org.Credibility)
	cveData = append(cveData, org.PublishedDate)
	return cveData
}

func ReadWriteExcel(excelPath, owner string, iss []models.IssueTemplate, is *IssueStr) (int64, error) {
	file, openErr := excelize.OpenFile(excelPath)
	if openErr != nil {
		logs.Error("fail to open the file, excelPath: ", excelPath, ", openErr: ", openErr)
		return iss[len(iss)-1].TemplateId, openErr
	}
	var templateId = int64(0)
	for _, its := range iss {
		templateId = its.TemplateId
		cveData := procIssueData(its, is, owner)
		if len(cveData) > 0 {
			rows, sheetErr := file.GetRows(sheetName)
			if sheetErr != nil {
				logs.Error(sheetErr)
			}
			idx := len(rows) + 1
			axis := fmt.Sprintf("A%d", idx)
			setErr := file.SetSheetRow(sheetName, axis, &cveData)
			if setErr != nil {
				logs.Error("setErr: ", setErr)
			}
		}
	}
	fileErr := file.SaveAs(excelPath)
	if fileErr != nil {
		logs.Error("Failed to save file, ", fileErr)
	}
	return templateId, fileErr
}

func procIssueData(its models.IssueTemplate, is *IssueStr, owner string) []interface{} {
	cveData := make([]interface{}, 0)
	if its.Owner != owner || len(its.IssueNum) < 2 {
		return cveData
	}
	is.CurIndex += 1
	is.cveCount += 1
	cveData = append(cveData, is.CurIndex)
	cveData = append(cveData, its.IssueNum)
	cveData = append(cveData, its.CveNum)
	cveData = append(cveData, its.Assignee)
	cveData = append(cveData, IssueStateConversion(its.Status))
	cveData = append(cveData, common.TimeConverStr(its.CreateTime.String()[:19]))
	cveData = append(cveData, its.Owner+"/"+its.OwnedComponent)
	cveData = append(cveData, its.NVDScore)
	cveData = append(cveData, models.OpenEulerScoreProc(its.NVDScore))
	if its.NVDScore >= 7 {
		is.HighCveCount += 1
	}
	return cveData
}

func QueryCveDesc(cveId int64) models.OriginUpstreamDesc {
	var desc models.OriginUpstreamDesc
	desc.CveId = cveId
	ordErr := models.GetOrgCveDesc(&desc, "CveId")
	if ordErr != nil {
		logs.Error("Failed to query data, GetOrgCveDesc, err: ", ordErr)
	}
	return desc
}

func ReadWriteCredExcel(excelPath string, org []models.OriginUpstream) (int64, error) {
	file, openErr := excelize.OpenFile(excelPath)
	if openErr != nil {
		logs.Error("fail to open the file, ", excelPath)
		return org[len(org)-1].CveId, openErr
	}
	tempCveId := int64(0)
	for _, cred := range org {
		its, _, _ :=models.QueryIssueTemplateByNum(cred.CveNum)
		if len(its) > 0 {
			models.UpdateOriginStatus(common.GetCurTime(), cred.PackName, cred.Version, cred.CveId, 6)
			continue
		}
		tempCveId = cred.CveId
		desc := QueryCveDesc(cred.CveId)
		cveData := procCredData(cred, desc)
		if len(cveData) > 0 {
			rows, sheetErr := file.GetRows(sheetName)
			if sheetErr != nil {
				logs.Error(sheetErr)
			}
			idx := len(rows) + 1
			axis := fmt.Sprintf("A%d", idx)
			setErr := file.SetSheetRow(sheetName, axis, &cveData)
			if setErr != nil {
				logs.Error("setErr: ", setErr)
			}
		}
		models.UpdateOriginStatus(common.GetCurTime(), cred.PackName, cred.Version, cred.CveId, 6)
	}
	fileErr := file.SaveAs(excelPath)
	if fileErr != nil {
		logs.Error("Failed to save file, ", fileErr)
	}
	return tempCveId, fileErr
}

func pressFileZip(excelPath, dir string) string {
	totalFileSlice := make([]string, 0)
	totalFileSlice = append(totalFileSlice, excelPath)
	zipFileName := "CVE_ISSUE_" + common.GetCurDate() + ".zip"
	zipFileName = filepath.Join(dir, zipFileName)
	zipErr := ZipFiles(zipFileName, totalFileSlice, dir, dir)
	if zipErr != nil {
		logs.Error("File compression failed: err: ", zipErr)
	}
	return zipFileName
}

func IssueStatistics(beforeMonth, prcnum int, owner string) error {
	excelPath, _ := createExcel(beforeMonth)
	if excelPath == "" {
		logs.Error("Failed to create file")
		return errors.New("Failed to create file")
	}
	fileSlice := make([]string, 0)
	templateId := int64(0)
	var is IssueStr
	for {
		it, err := GetIssueData(beforeMonth, prcnum, templateId)
		if err != nil {
			logs.Error("GetIssueData, err: ", err)
			break
		}
		logs.Info("it==>", it)
		if len(it) == 0 {
			break
		}
		templateId, err = ReadWriteExcel(excelPath, owner, it, &is)
		if err != nil {
			templateId = 0
			break
		}
	}
	zipFileName := excelPath
	if is.cveCount > 0 {
		if templateId > 0 {
			cBody := fmt.Sprintf("hi all: \r\n 当前未解决漏洞有" + strconv.FormatInt(is.cveCount, 10) + "个, 其中" +
				strconv.FormatInt(is.HighCveCount, 10) + "个7分以上漏洞, 详情见附件, 7分以上CVE请在周三之前解决. \r\n" +
				"已经分析完毕的issue请maintainer尽快关掉, 否则影响数据统计; 提交PR时要关联issue, 若CVE在之前PR解决, 请PR提交人编辑PR信息将issue关联上. \r\n")
			sendError := SendEmail(zipFileName, 2, cBody, "")
			if sendError != nil {
				logs.Error("SendEmail, sendErr: ", sendError)
			}
		}
	}
	fileSlice = append(fileSlice, excelPath)
	fileSlice = append(fileSlice, zipFileName)
	DelFile(fileSlice)
	return nil
}

func CveCredibilityStatistics(beforeDate, prcnum int) error {
	credibilityLevel, ok := beego.AppConfig.Int("cve::credibility_level")
	if ok != nil {
		credibilityLevel = 3
	}
	beforeTime := common.GetSpecialDate(beforeDate)
	excelPath, _ := CveCredExcel()
	if excelPath == "" {
		logs.Error("Failed to create file")
		return errors.New("Failed to create file")
	}
	tempCveId := int64(0)
	fileSlice := make([]string, 0)
	for {
		org, num, err := models.QueryLowCredibilityCve(beforeTime, prcnum, credibilityLevel, tempCveId)
		if len(org) > 0 {
			tempCveId, err = ReadWriteCredExcel(excelPath, org)
			if err != nil {
				tempCveId = 0
				break
			}
		} else {
			logs.Error("failed: ", num, ", err: ", err)
			break
		}
	}
	zipFileName := excelPath
	if tempCveId > 0 {
		//zipFileName := pressFileZip(excelPath, dir)
		cBody := fmt.Sprintf("hi all: \r\n 当前中科院上传的可信度为4/5/6等级的数据, " +
			"请见附件! 如果有需要提交issue的CVE, 请整理成人工提交CVE的excel格式, 工具进行处理. \r\n")
		subject := "可信度较低的cve漏洞反馈"
		sendError := SendEmail(zipFileName, 3, cBody, subject)
		if sendError != nil {
			logs.Error("SendEmail, sendErr: ", sendError)
			return sendError
		}
	}
	fileSlice = append(fileSlice, excelPath)
	fileSlice = append(fileSlice, zipFileName)
	DelFile(fileSlice)
	return nil
}

func ProcSecLinkTemplate(beforeDate, prcnum int, owner, accessToken string) error {
	secLinkConfig := beego.AppConfig.String("reflink::openeuler_web")
	templateId := int64(0)
	for {
		it, err := GetNoLinkIssueData(beforeDate, prcnum, templateId)
		if err != nil {
			logs.Error("GetNoLinkIssueData, err: ", err)
			break
		}
		logs.Info("it==>", it)
		if len(it) == 0 {
			break
		}
		for _, temp := range it {
			templateId = temp.TemplateId
			cveCenter := models.VulnCenter{CveId: temp.CveId, CveNum: temp.CveNum}
			cveErr := models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num")
			if cveErr != nil || cveCenter.OrganizationID != 1 {
				continue
			}
			// Determine whether cve has been processed
			issueExist, saData := GetCveSecurityNotice(temp.CveNum, temp.Repo)
			if issueExist && len(saData.Result.AffectedProduct) > 2 {
				// Update sa release time
				UpdateSAReleaseTime(saData.Result.AffectedProduct, saData.Result.CreateTime, temp.TemplateId)
				secLink := secLinkConfig + "/zh/security/safety-bulletin/detail.html?id=" + saData.Result.AffectedProduct
				if temp.SecLink != secLink {
					temp.SecLink = secLink
				} else {
					continue
				}
				_, issueErr := UpdateIssueToGit(accessToken, owner, temp.Repo,
					cveCenter, temp)
				logs.Info("ProcSecLinkTemplate1, UpdateIssueToGit, err: ", issueErr)
				// release
				temp.IssueStatus = 5
				tempErr := models.UpdateIssueTemplate(&temp, "SecLink", "IssueStatus")
				logs.Info("ProcSecLinkTemplate1, UpdateIssueTemplate, err: ", tempErr)
			} else {
				if len(temp.SecLink) > 2 {
					temp.SecLink = ""
					_, issueErr := UpdateIssueToGit(accessToken, owner, temp.Repo,
						cveCenter, temp)
					logs.Info("ProcSecLinkTemplate2, UpdateIssueToGit, err: ", issueErr)
					// release
					temp.IssueStatus = 5
					tempErr := models.UpdateIssueTemplate(&temp, "SecLink", "IssueStatus")
					logs.Info("ProcSecLinkTemplate2, UpdateIssueTemplate, err: ", tempErr)
				}
			}
		}
	}
	return nil
}

// Update the release time of sa
func UpdateSAReleaseTime(saNum, saReleaseTime string, templateId int64) {
	// query issue
	ita := models.IssueTemplateAssociation{TemplateId: templateId}
	tempErr := models.QueryIssueTemplateAssociation(&ita, "templateId")
	if tempErr == nil {
		ita.SaReleaseTime = saReleaseTime
		updateErr := models.UpdateIssueTemplateAssociation(&ita, "SaReleaseTime")
		if updateErr != nil {
			logs.Error("UpdateIssueTemplateAssociation, err: ", updateErr)
		}
	} else {
		ita.TemplateId = templateId
		ita.SaReleaseTime = saReleaseTime
		_, insertErr := models.InsertIssueTemplateAssociation(&ita)
		if insertErr != nil {
			logs.Error("InsertIssueTemplateAssociation, err: ", insertErr)
		}
	}
}
